<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Deferred 源码解析</title>
</head>
<body>
    <script>
        jQuery.extend({
            Deferred: function( func ) {
                var tuples = [
                        // action, add listener, listener list, final state
                        [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                        [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                        [ "notify", "progress", jQuery.Callbacks("memory") ]
                    ],
                    state = "pending",
                    promise = {
                        state: function() {
                            return state;
                        },
                        always: function() {
                            deferred.done( arguments ).fail( arguments );
                            return this;
                        },
                        then: function( /* fnDone, fnFail, fnProgress */ ) {
                            var fns = arguments;
                            return jQuery.Deferred(function( newDefer ) {
                                jQuery.each( tuples, function( i, tuple ) {
                                    var action = tuple[ 0 ],
                                        fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
                                    // deferred[ done | fail | progress ] for forwarding actions to newDefer
                                    deferred[ tuple[1] ](function() {
                                        var returned = fn && fn.apply( this, arguments );
                                        if ( returned && jQuery.isFunction( returned.promise ) ) {
                                            returned.promise()
                                                .done( newDefer.resolve )
                                                .fail( newDefer.reject )
                                                .progress( newDefer.notify );
                                        } else {
                                            newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                                        }
                                    });
                                });
                                fns = null;
                            }).promise();
                        },
                        // Get a promise for this deferred
                        // If obj is provided, the promise aspect is added to the object
                        // 将primise对象合并到最终的Deferred对象中，最终返回deferred对象
                        promise: function( obj ) {
                            return obj != null ? jQuery.extend( obj, promise ) : promise;
                        }
                    },
                    deferred = {};

                // Keep pipe for back-compat
                promise.pipe = promise.then;

                // Add list-specific methods
                jQuery.each( tuples, function( i, tuple ) {
                    var list = tuple[ 2 ],
                        stateString = tuple[ 3 ];

                    // promise[ done | fail | progress ] = list.add
                    promise[ tuple[1] ] = list.add;

                    // Handle state
                    if ( stateString ) {
                        /**
                         * i ^ 1 按位取反 eg: 0^1=1   1^1=0
                         * 向每个回调队列里添加回调函数
                         * 第一个函数，是一个匿名函数，用来改变执行状态的
                         * 第二个函数，disable 禁用回调函数的执行，eg: 执行resolve()方法，禁用掉reject_list队列的回调函数；执行reject()方法，禁用掉resolve_list队列的回调函数
                         * 第三个函数，lock 上锁，progress_list队列的回调函数上锁，保留当前状态
                         */ 
                        list.add(function() {
                            // state = [ resolved | rejected ]
                            state = stateString;

                        // [ reject_list | resolve_list ].disable; progress_list.lock
                        }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
                    }

                    /**
                     * deferred 的 resolve，reject, notify 方法调用的是 resolveWith、rejectWith、notifyWith 方法
                     * deferred 的 resolveWith、rejectWith、notifyWith 方法对应 Callbacks 对象的 fireWith 方法
                     * 总结：deferred 的 resolve，reject, notify 最终调用的就是 Callbacks 对象的 fireWith 方法，传递了对应的参数
                     * 如果调用 resolveWith 的调用者是 deferred 对象，则调用 fireWith 时传入 promise，如果调用 resolveWith 不是 deferred 对象，调用fireWith时传入deferred
                    */
                    // deferred[ resolve | reject | notify ]
                    deferred[ tuple[0] ] = function() {
                        // deferred[ resolveWith | rejectWith | notifyWith ](deferred | promise, arguments) 函数调用
                        deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
                        return this;
                    };
                    // deferred[ resolveWith | rejectWith | notifyWith ] = list.fireWith
                    deferred[ tuple[0] + "With" ] = list.fireWith;
                });

                // Make the deferred a promise
                promise.promise( deferred );

                // Call given func if any
                if ( func ) {
                    func.call( deferred, deferred );
                }

                // All done!
                return deferred;
            },
            
            // Deferred helper
            when: function( subordinate /* , ..., subordinateN */ ) {
                var i = 0,
                    resolveValues = core_slice.call( arguments ),
                    length = resolveValues.length,

                    // the count of uncompleted subordinates
                    remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

                    // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
                    deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

                    // Update function for both resolve and progress values
                    updateFunc = function( i, contexts, values ) {
                        return function( value ) {
                            contexts[ i ] = this;
                            values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
                            if( values === progressValues ) {
                                deferred.notifyWith( contexts, values );
                            } else if ( !( --remaining ) ) {
                                deferred.resolveWith( contexts, values );
                            }
                        };
                    },

                    progressValues, progressContexts, resolveContexts;

                // add listeners to Deferred subordinates; treat others as resolved
                if ( length > 1 ) {
                    progressValues = new Array( length );
                    progressContexts = new Array( length );
                    resolveContexts = new Array( length );
                    for ( ; i < length; i++ ) {
                        if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
                            resolveValues[ i ].promise()
                                .done( updateFunc( i, resolveContexts, resolveValues ) )
                                .fail( deferred.reject )
                                .progress( updateFunc( i, progressContexts, progressValues ) );
                        } else {
                            --remaining;
                        }
                    }
                }

                // if we're not waiting on anything, resolve the master
                if ( !remaining ) {
                    deferred.resolveWith( resolveContexts, resolveValues );
                }

                return deferred.promise();
            }
        })
    </script>
</body>
</html>